//----------------------------------------------
// Multiplexing Server using select()
// ------------------------
// Author : Woo-Hyun, Kim
// Date : Feb. 12, 2004
// Email : woorung@empal.com             
//----------------------------------------------
#include <iostream>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//----------------------------------------------
#ifdef _WIN32  // on Windows
#include <winsock2.h>
#else  // on Linux
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/time.h> // for using struct timeval as the timeout parameter of select();
#include <signal.h>
#endif
//----------------------------------------------
// for half-close like shutdown() on Windows
#ifdef _WIN32
#define SHUT_RD SD_RECEIVE
#define SHUT_WR SD_SEND
#define SHUT_RDWR SD_BOTH
#endif
//-----------------------------------------------
#define WAIT_QUEUE 10
#define SRV_PORT "2020"
#define BUF_SIZE 1024
//-----------------------------------------------
using namespace std;
//-----------------------------------------------
void connected_proc(int clnt_sock,struct sockaddr_in *pclnt_addr)
{
	cout << "connected from " << inet_ntoa(pclnt_addr->sin_addr) << endl;

	// write your code you want to do just after connected from a client socket
	// ...
}
//----------------------------------------------
int read_proc(int clnt_sock)
{
	int recv_len;
	char recv_buf[BUF_SIZE];
	
	recv_len= recv(clnt_sock,recv_buf,BUF_SIZE,0);
	if(recv_len==0) return -1; // in case of EOF, that is, close from the client socket

	// write your code about the data received from a client socket
	// ...
	send(clnt_sock,recv_buf,recv_len,0);

	return 0;
}
//----------------------------------------------
int main(int argc,char *argv[])
{
#ifdef _WIN32
	SOCKET srv_sock;	
#else
	int srv_sock;
#endif
	struct sockaddr_in srv_addr;

#ifdef _WIN32
	WSADATA wsaData;

	// for linking ws2_32.lib on Windows
	if(WSAStartup(0x0202,&wsaData)!=0) {
		cout << "WSAStartup() error" << endl;
		exit(-1);
	}
#endif

#ifdef _WIN32
	SOCKET max_fd;
#else
	int max_fd;
#endif
	struct timeval timeout;
	fd_set read_set, read_tset;
	fd_set write_set, write_tset;
	fd_set urgent_set, urgent_tset;

	srv_sock = socket(AF_INET,SOCK_STREAM,0);
	if(srv_sock<0) {
		cout << "socket() error" << endl;
#ifdef _WIN32
		// for releasing ws2_32.lib on Windows
		WSACleanup();
#endif
		exit(-1);
	}

	memset(&srv_addr,0,sizeof(srv_addr));
	srv_addr.sin_family = AF_INET;
	srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	srv_addr.sin_port = htons(atoi(SRV_PORT));

	if(bind(srv_sock,(struct sockaddr *)&srv_addr,sizeof(srv_addr))<0) {
		cout << "bind() error" << endl;
#ifdef _WIN32
		closesocket(srv_sock);
		// for releasing ws2_32.lib on Windows
		WSACleanup();
#else
		close(srv_sock);
#endif
		exit(-1);
	}

	if(listen(srv_sock,WAIT_QUEUE)<0) {
		cout << "listen() error" << endl;
#ifdef _WIN32
		closesocket(srv_sock);
		// for releasing ws2_32.lib on Windows
		WSACleanup();
#else
		close(srv_sock);
#endif
		exit(-1);
	}

	// initialize fd_set datatype
	FD_ZERO(&read_set);
	FD_ZERO(&write_set);
	FD_ZERO(&urgent_set);
	
	// set the server socket inspected by means of using select()
	FD_SET(srv_sock,&read_set);
	max_fd = srv_sock; // if on Windows, max_fd is meaningless with select() function.

	// finally, select() get started
	while(1) {
		read_tset = read_set;
		write_tset = write_set;
		urgent_tset = urgent_set;

		// set timeout when selecting
		timeout.tv_sec = 5;
		timeout.tv_usec = 0;

		if(select(max_fd+1,&read_tset,&write_tset,&urgent_tset,&timeout)<0) {
			cout << "select() error" << endl;
#ifdef _WIN32
			closesocket(srv_sock);
			// for releasing ws2_32.lib on Windows
			WSACleanup();
#else
			close(srv_sock);
#endif
			exit(-1);
		}

		// deal with sockets changed by select()
#ifdef _WIN32
		SOCKET fd;
#else
		int fd;
#endif
		
		for(fd=0;fd<max_fd+1;fd++) {
			if(FD_ISSET(fd,&read_tset)) {
				if(fd==srv_sock) { // happen when there is a client socekt accepted from the server socket
#ifdef _WIN32
					SOCKET clnt_sock;
#else
					int clnt_sock;
#endif
					struct sockaddr_in clnt_addr;
					socklen_t clnt_len = sizeof(clnt_addr);
					
					if((clnt_sock=accept(srv_sock,(struct sockaddr *)&clnt_addr,&clnt_len))<0) {
						cout << "accept() error" << endl;
#ifdef _WIN32
						closesocket(srv_sock);
						// for releasing ws2_32.lib on Windows
						WSACleanup();
#else
						close(srv_sock);
#endif
						exit(-1);
					}

					// ready to select the new client socket accepted by the server socket
					FD_SET(clnt_sock,&read_set);
					FD_SET(clnt_sock,&write_set);
					FD_SET(clnt_sock,&urgent_set);

					// set max_fd newly to specifiy the range of selecting
					if(max_fd<clnt_sock) max_fd = clnt_sock;

					// process something after connected to a client socket
					connected_proc(clnt_sock,&clnt_addr);
				}
				else { // happen when there are some data to read from a client socket
					cout << "read : " << fd << endl;
					if(read_proc(fd)<0) {
						// the data received from a client socket were a request to close the socket
						// therefore, delete the client socket from read_set
						FD_CLR(fd,&read_set);
						FD_CLR(fd,&write_set);
						FD_CLR(fd,&urgent_set);
#ifdef _WIN32
						closesocket(fd);
#else
						close(fd);
#endif
					}
				}
			}
			else if(FD_ISSET(fd,&write_tset)) {
			
			}
			else if(FD_ISSET(fd,&urgent_tset)) {
			
			}			
		}
	}

#ifdef _WIN32
	closesocket(srv_sock);
	// for releasing ws2_32.lib on Windows
	WSACleanup();
#else
	close(srv_sock);
#endif
	return 0;
}
